#include "stdafx.h"
#include "Texture.h"
#include "Pushers.h"
#include <Utilities\Exceptions.h>
#include <Utilities\Extensions.h>

using namespace std;

Texture::Texture(GLenum target, GLenum proxyTarget)
:_target(target), _proxyTarget(proxyTarget)
{

}

Texture::~Texture(void)
{
	glDeleteTextures(static_cast<GLsizei>(_textureId.size()), &_textureId[0]);
}

void Texture::Create(const RadiosityTexture textureStyleSelection, const GLint internalFormat, const GLenum format, const GLenum type, int width, int height)
{
	Create(textureStyleSelection, internalFormat, format, type, width, height, 0);
}

void Texture::Create(const RadiosityTexture textureStyleSelection, const GLint internalFormat, const GLenum format, const GLenum type, int width, int height, const int depth)
{
	PushAttribute textureBit(GL_TEXTURE_BIT);

	if((textureStyleSelection == Ij) || (textureStyleSelection == DeltaIj) || (textureStyleSelection == Rj))
	{
		width = max(width / 2, 1);
		height = max(height / 2, 1);
	}

	glGenTextures(static_cast<GLsizei>(_textureId.size()), &_textureId[0]);
	for(vector<GLuint>::iterator iter = _textureId.begin(); iter != _textureId.end(); iter++)
	{
		glBindTexture(_target, *iter);

		switch(textureStyleSelection)
		{
		case Bj:
		case Ij:
		case Rj:
			{
				glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
				glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
				glTexParameteri(_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
				glTexParameteri(_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
				break;
			}
		case DeltaBj:
		case DeltaIj:
		case Array:
			{
				glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
				glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
				glTexParameteri(_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
				glTexParameteri(_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
				break;
			}
		case ItemBuffer:
			{
				glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
				glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
				break;
			}
		}

		//Check to see if we can create the texture with the requested format parameters
		int proxyWidth = 0, proxyHeight = 0;
		CallTexImage(_proxyTarget, internalFormat, width, height, depth, format, type);
		glGetTexLevelParameteriv(_proxyTarget, 0, GL_TEXTURE_WIDTH, &proxyWidth);
		glGetTexLevelParameteriv(_proxyTarget, 0, GL_TEXTURE_HEIGHT, &proxyHeight);

		//If the returned width or height are zero, this texture can't be created
		if(proxyWidth != width || proxyHeight != height)
		{
			ostringstream str;
			str << "Unable to allocate necessary texture memory." << ends;
			throw GraphicsCapabilityException(str.str());
		}

		CallTexImage(_target, internalFormat, width, height, depth, format, type);

		if(textureStyleSelection == DeltaBj || textureStyleSelection == DeltaIj || textureStyleSelection == Array)
		{
			glGenerateMipmapEXT(_target);
		}
	}
}

void Texture::RegenerateMipmap(void) const
{
	PushAttribute textureBit(GL_TEXTURE_BIT);
	BindTexture();

	glGenerateMipmapEXT(_target);
}

const int Texture::GetTopLevel(void) const
{
	Vec2i size = GetTextureSize();
	return static_cast<int>(floor(log(static_cast<float>(max(size[Width], size[Height])))*1.4426950));
}

void Texture::BindTexture() const
{
	glBindTexture(GetTarget(), GetReadId());
}

void Texture::BindTexture(const GLenum textureTarget) const
{
	glActiveTexture(textureTarget);
	BindTexture();
}

const GLenum Texture::GetTarget(void) const
{
	return _target;
}

Texture2D::Texture2D(void)
:Texture(GL_TEXTURE_2D, GL_PROXY_TEXTURE_2D)
{
	_textureId.resize(1);
}

Texture2D::Texture2D(const RadiosityTexture textureStyleSelection, const GLint internalFormat, const GLenum format, const GLenum type, int width, int height)
:Texture(GL_TEXTURE_2D, GL_PROXY_TEXTURE_2D)
{
	_textureId.resize(1);
	Create(textureStyleSelection, internalFormat, format, type, width, height);
}

const GLuint Texture2D::GetReadId(void) const
{
	return _textureId[0]; 
}

void Texture2D::CallTexImage(const GLenum target, const GLint internalFormat, const GLsizei width, const GLsizei height, const GLsizei depth, const GLenum format, const GLenum type)
{
	depth;

	glTexImage2D(target, 0, internalFormat, width, height, 0, format, type, NULL);
}

Texture2DRW::Texture2DRW(void)
:Texture(GL_TEXTURE_2D, GL_PROXY_TEXTURE_2D)
{
	_textureId.resize(2);
}

Texture2DRW::Texture2DRW(const RadiosityTexture textureStyleSelection, const GLint internalFormat, const GLenum format, const GLenum type, int width, int height)
:Texture(GL_TEXTURE_2D, GL_PROXY_TEXTURE_2D)
{
	_textureId.resize(2);
	Create(textureStyleSelection, internalFormat, format, type, width, height);
}

const GLuint Texture2DRW::GetReadId(void) const
{
	return _swapTextures ? _textureId[0] : _textureId[1]; 
}

const GLuint Texture2DRW::GetWriteId(void) const
{
	return _swapTextures ? _textureId[1] : _textureId[0]; 
}

void Texture2DRW::SwapReadWrite(void)
{
	_swapTextures = !_swapTextures; 
}

void Texture2DRW::CallTexImage(const GLenum target, const GLint internalFormat, const GLsizei width, const GLsizei height, const GLsizei depth, const GLenum format, const GLenum type)
{
	depth;

	glTexImage2D(target, 0, internalFormat, width, height, 0, format, type, NULL);
}

TextureRectRW::TextureRectRW(void)
:Texture(GL_TEXTURE_RECTANGLE_ARB, GL_PROXY_TEXTURE_RECTANGLE_ARB)
{
	_textureId.resize(2);
}

TextureRectRW::TextureRectRW(const RadiosityTexture textureStyleSelection, const GLint internalFormat, const GLenum format, const GLenum type, int width, int height)
:Texture(GL_TEXTURE_RECTANGLE_ARB, GL_PROXY_TEXTURE_RECTANGLE_ARB)
{
	_textureId.resize(2);
	Create(textureStyleSelection, internalFormat, format, type, width, height);
}

const GLuint TextureRectRW::GetReadId(void) const
{
	return _swapTextures ? _textureId[0] : _textureId[1]; 
}

const GLuint TextureRectRW::GetWriteId(void) const
{
	return _swapTextures ? _textureId[1] : _textureId[0]; 
}

void TextureRectRW::SwapReadWrite(void)
{
	_swapTextures = !_swapTextures; 
}

void TextureRectRW::CallTexImage(const GLenum target, const GLint internalFormat, const GLsizei width, const GLsizei height, const GLsizei depth, const GLenum format, const GLenum type)
{
	depth;

	glTexImage2D(target, 0, internalFormat, width, height, 0, format, type, NULL);
}

TextureArray::TextureArray(void)
:Texture(GL_TEXTURE_2D_ARRAY_EXT, GL_PROXY_TEXTURE_2D_ARRAY_EXT)
{
	_textureId.resize(1);
}

TextureArray::TextureArray(const RadiosityTexture textureStyleSelection, const GLint internalFormat, const GLenum format, const GLenum type, const int width, const int height, const int depth)
:Texture(GL_TEXTURE_2D_ARRAY_EXT, GL_PROXY_TEXTURE_2D_ARRAY_EXT)
{
	_textureId.resize(1);
	Create(textureStyleSelection, internalFormat, format, type, const_cast<int&>(width), const_cast<int&>(height), depth);
}

const GLuint TextureArray::GetReadId(void) const
{
	return _textureId[0]; 
}

void TextureArray::CallTexImage(const GLenum target, const GLint internalFormat, const GLsizei width, const GLsizei height, const GLsizei depth, const GLenum format, const GLenum type)
{
	glTexImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, NULL);
}

